package model

import (
	"errors"
	"fmt"
	"github.com/chicken-team-outside/chicken_transmission/proxy_server/setting"
	"go.uber.org/zap"
	"gorm.io/gorm"
	"strconv"
	"sync"
	"time"
)

var AddTransportHook func(info *TransportInfo)
var RemoveTransportHook func(info *TransportInfo)
var UpdateTransportHook func(info *TransportInfo)

type TransportInfo struct {
	Id          uint64    `gorm:"colum:id;primaryKey;autoIncrement" json:"id"`
	UserId      uint64    `gorm:"colum:user_id;not null" json:"user_id"`
	Title       string    `gorm:"colum:title" json:"title"`
	Token       string    `gorm:"colum:token;not null;unique" json:"token"`
	IsActive    bool      `gorm:"colum:is_active" json:"is_active"`
	UpdatedTime time.Time `gorm:"colum:updated_time;autoUpdateTime" json:"updated_time"`
	CreateTime  time.Time `gorm:"colum:create_time;autoCreateTime" json:"create_time"`
}

func (t *TransportInfo) TableName() string {
	return "transport_info"
}

func ReplaceTransportInfo(info *TransportInfo) error {
	var c int64
	setting.Db.Model(&TransportInfo{}).Where("token = ? AND user_id = ?", info.Token, info.UserId).Limit(1).Count(&c)
	if c > 0 {
		err := setting.Db.Model(info).Where("user_id=? AND  token=?", info.UserId, info.Token).Updates(map[string]interface{}{
			"title":     info.Title,
			"is_active": info.IsActive,
		}).Error
		if err == nil {
			info, err = getTransportInfoByTokenOrIdFromDB(info.Token, 0)
			if err == nil {
				CacheTransportInfo(info)
				if UpdateTransportHook != nil {
					UpdateTransportHook(info)
				}
			}
		}
		return err
	}
	err := setting.Db.Create(info).Error
	if err != nil {
		return err
	}
	UpdateRelation(info.UserId, info.Id, true)
	if AddTransportHook != nil {
		AddTransportHook(info)
	}
	return nil
}

var infoCache map[string]*TransportInfo
var infoCacheLock sync.RWMutex
var relationCache map[uint64]map[uint64]struct{}
var relationCacheLock sync.RWMutex

func init() {
	infoCache = make(map[string]*TransportInfo)
	relationCache = make(map[uint64]map[uint64]struct{})
}

func CacheTransportInfo(info *TransportInfo) {
	infoCacheLock.Lock()
	defer infoCacheLock.Unlock()
	infoCache[info.Token] = info
	infoCache[fmt.Sprintf("%d", info.Id)] = info
}

func DeleteCacheTransportInfo(token string) {
	infoCacheLock.Lock()
	info, _ := infoCache[token]
	if info != nil {
		delete(infoCache, info.Token)
		delete(infoCache, strconv.FormatUint(info.Id, 10))
	}
	infoCacheLock.Unlock()
	if info != nil {
		UpdateRelation(info.UserId, info.Id, false)
		if RemoveTransportHook != nil {
			RemoveTransportHook(info)
		}
	}
}

func getTransportInfoByTokenOrIdFromDB(token string, id uint64) (info *TransportInfo, err error) {
	info = &TransportInfo{}
	if token == "" {
		err = setting.Db.Where("id = ?", id).First(&info).Error
	} else {
		err = setting.Db.Where("token = ?", token).First(&info).Error
	}
	if err != nil {
		info = nil
		if errors.Is(err, gorm.ErrRecordNotFound) {
			err = nil
		}
	}
	return
}

func GetTransportInfoByTokenOrId(token string, id uint64) (info *TransportInfo, err error) {
	infoCacheLock.RLock()
	if token == "" {
		info, _ = infoCache[strconv.FormatUint(id, 10)]
	} else {
		info, _ = infoCache[token]
	}
	infoCacheLock.RUnlock()
	if info != nil {
		return
	}
	info, err = getTransportInfoByTokenOrIdFromDB(token, id)
	if info != nil {
		CacheTransportInfo(info)
	}
	return
}

func GetUserTransportInfos(userId uint64) (infos []*TransportInfo, err error) {
	err = setting.Db.Where("user_id = ?", userId).Find(&infos).Error
	return
}

func DeleteTransportInfo(userId uint64, token string) error {
	r := setting.Db.Delete(&TransportInfo{}, "user_id = ? AND token = ?", userId, token)
	if r.RowsAffected > 0 {
		DeleteCacheTransportInfo(token)
	}
	return r.Error
}

func GetRelations(usrId uint64) (ids map[uint64]struct{}) {
	relationCacheLock.RLock()
	ids, _ = relationCache[usrId]
	relationCacheLock.RUnlock()
	if ids == nil {
		var idList []uint64
		err := setting.Db.Model(&TransportInfo{}).Select("id").Where("user_id = ?", usrId).Find(&idList).Error
		if err != nil {
			zap.L().Warn("failed to transport relation infos", zap.Error(err), zap.Uint64("user_id", usrId))
		} else {
			ids = make(map[uint64]struct{})
			for _, id := range idList {
				ids[id] = struct{}{}
			}
			relationCacheLock.Lock()
			relationCache[usrId] = ids
			relationCacheLock.Unlock()
		}
	}
	return
}

func UpdateRelation(usrId uint64, clientId uint64, add bool) {
	ids := GetRelations(usrId)
	var newIds map[uint64]struct{}
	if add {
		newIds = make(map[uint64]struct{}, len(ids)+1)
		for k, v := range ids {
			newIds[k] = v
		}
		newIds[clientId] = struct{}{}
	} else {
		newIds = make(map[uint64]struct{}, len(ids)+1)
		for k, v := range ids {
			if k == clientId {
				continue
			}
			newIds[k] = v
		}
	}
	relationCacheLock.Lock()
	relationCache[usrId] = newIds
	relationCacheLock.Unlock()
}
